import os
import math
import json
import datetime
from pathlib import Path
from modules.context import create_global_context
import modules.YamlParser as YamlParser
from jinja2 import Environment, FileSystemLoader
from loguru import logger

class CCodeGenerator:
    def __init__(self, excels):
        self.mctx = create_global_context()
        self.mtmp = Path(os.path.join(self.mctx.work_dir, "..", "templates"))
        self.menv = Environment(loader=FileSystemLoader(self.mtmp.absolute()))
        self.menv.filters['add'] = self._add_filter
        self.mexcels = excels
    
    def _add_filter(self, value, delta):
        return value + delta
    
    """
    生成寄存器头文件

    @param outdir: 输出目录
    """
    def _generateRegistersHeadFile(self, outdir: str):
        analyseWsSet = list()


        # 将 worksheet 和 yaml 中的 slave 关联起来
        for excel in self.mexcels:
            for yexcel in self.mctx.yaml.excels:
                if os.path.basename(excel["file"]) == os.path.basename(yexcel["path"]):
                    for ws in excel["worksheets"]:
                        registerFileTemplate = self.menv.get_template("h_register_file.jinja2")
                        registerStructTemplate = self.menv.get_template("h_register/h_register_struct.jinja2")
                        registerEnumTemplate = self.menv.get_template("h_register/h_enum_define.jinja2")
                        registerAddressTemplate = self.menv.get_template("h_register/h_register_address.jinja2")
                        registerStructBitsTemplate = self.menv.get_template("h_register/h_structbits_define.jinja2")

                        # logger.debug(json.dumps(ws, indent=4))
                        for yws in yexcel["sheets"]:
                            if ws["ws_name"] == yws["name"]:
                                analyseWsSet.append({
                                    "ws_name": ws["ws_name"],
                                })
                                formatEnums = list()
                                formatStructs = list()
                                formatAddresses = list()
                                formatStructBitss = list()

                                # 生成枚举定义
                                for enum in ws["table"]["enums"]["enums"]:
                                    formatEnums.append(registerEnumTemplate.render(
                                        enumName=enum["name"],
                                        enumNamePrefix=yws.get("c_code").get("enum_prefix"),
                                        enumNameSuffix=yws.get("c_code").get("enum_suffix"),
                                        enumElementPrefix='' if yws["c_code"].get("enum_element_prefix") is None else yws["c_code"].get("enum_element_prefix") + enum["name"],
                                        enumElementSuffix='' if yws["c_code"].get("enum_element_suffix") is None else yws["c_code"].get("enum_element_suffix"),
                                        enums=enum["elements"]
                                    ))

                                # 生成bits结构体定义
                                for bits in ws["table"]["struct_bitss"]["structs"]:
                                    elements = list()
                                    currentBit = 0
                                    # 计算总位数
                                    total_bits = max([e["bit_start"] + e["bit_len"] for e in bits["elements"]])
                                    bits["total_bits"] = total_bits

                                    for element in sorted(bits["elements"], key=lambda x: x["bit_start"]):
                                        # 如果当前字段的 bit_start 大于 currentBit，则需要插入预留位
                                        if element["bit_start"] > currentBit:
                                            elements.append({
                                                "name": f"reserved_{len(elements) + 1}",
                                                "bit_start": currentBit,
                                                "bit_len": element["bit_start"] - currentBit,
                                                "description": "预留位"
                                            })
                                        # 添加当前字段
                                        elements.append(element)
                                        # 更新 currentBit
                                        currentBit = element["bit_start"] + element["bit_len"]
                                    
                                    # 如果最后还有剩余位，添加末尾预留位
                                    total_bits = 32 if bits["total_bits"] > 16 else 16
                                    if currentBit < total_bits:
                                        elements.append({
                                            "name": "reserved_end",
                                            "bit_start": currentBit,
                                            "bit_len": total_bits - currentBit,
                                            "description": "末尾预留位"
                                        })
                                    
                                    # 更新结构体的元素
                                    bits["elements"] = elements    
                                    # 渲染枚举
                                    for enum in bits["elements"]:
                                        # print(enum)
                                        if "enum" in enum and enum["enum"]:
                                            formatStructBitss.append(registerEnumTemplate.render(
                                                enumName=enum["name"],
                                                enumNamePrefix=yws.get("c_code").get("enum_prefix"),
                                                enumNameSuffix=yws.get("c_code").get("enum_suffix"),
                                                enumElementPrefix='' if yws["c_code"].get("enum_element_prefix") is None else yws["c_code"].get("enum_element_prefix") + enum["name"],
                                                enumElementSuffix='' if yws["c_code"].get("enum_element_suffix") is None else yws["c_code"].get("enum_element_suffix"),
                                                enums=enum["enum"]
                                            ))                                
                                    # print(bits)
                                    formatStructBitss.append(registerStructBitsTemplate.render(struct = bits))
                                # print(formatStructBitss)

                                if ws["table"]["registers"] != None:
                                    cRegisters = list()
                                    for register in ws["table"]["registers"]:
                                        cRegister = register.copy()
                                        # 数组类型
                                        if register["type"] == "enum16":
                                            cRegister["type"] = "uint16_t"
                                            cRegister["size"] = 1
                                        elif register["type"] == "enum32":
                                            cRegister["type"] = "uint32_t"
                                            cRegister["size"] = 1
                                        # 非数组的寄存器类型
                                        elif register["type"] == "uint32_t" or register["type"] == "int32_t":
                                            cRegister["size"] = int(math.ceil(register["length"] / 2))
                                        # 线圈类型
                                        else:
                                            cRegister["size"] = register["length"]
                                        # 是否生成原子变量
                                        cRegister["code_atomic"] = yws["c_code"]["code_atomic"]
                                        # 填充枚举全称
                                        if cRegister.get("enum_name") != None and cRegister["enum_name"] != "":
                                            prefix = '' if yws.get("c_code").get("enum_prefix") is None else yws.get("c_code").get("enum_prefix")
                                            suffix = '' if yws.get("c_code").get("enum_suffix") is None else yws.get("c_code").get("enum_suffix")
                                            cRegister["enum"] = prefix + cRegister["enum_name"] + suffix
                                        cRegisters.append(cRegister)
                                    # logger.debug(json.dumps(cRegisters, indent=4))

                                    formatStructs.append(registerStructTemplate.render(
                                        c_code=yws["c_code"],
                                        registers=cRegisters
                                    ))

                                    formatAddresses.append(registerAddressTemplate.render(
                                        c_code=yws["c_code"],
                                        registers=cRegisters
                                    ))

                                with open(os.path.join(outdir, f"{yws['c_code']['file_name']}.h"), "w", encoding="utf-8") as f:
                                    f.write(registerFileTemplate.render(
                                        excelName=os.path.basename(f"{yexcel['path']}"),
                                        sheetName=yws["name"],
                                        slave_name=yws["slave"]["name"],
                                        c_code=yws['c_code'],
                                        structs=formatStructs,
                                        version=self.mctx.version,
                                        date=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                        enums=formatEnums,
                                        structbitss=formatStructBitss,
                                        ids=yws["slave"]["addresses"],
                                        addresses=formatAddresses
                                    ))
        
        for analyseWs in analyseWsSet:
            logger.info(f"成功生成头文件: {analyseWs['ws_name']}")
        for ws in excel["worksheets"]:
            index = 0
            for index, analyseWs in enumerate(analyseWsSet):
                if analyseWs["ws_name"] == ws["ws_name"]:
                    break
            if index == len(analyseWsSet):
                logger.warning(f"未解析工作表: {ws['ws_name']}")

    def _generateAgileSlaveCode(self, outdir: str):
        analyseWsSet = list()
        
        for excel in self.mexcels:
            # 将 worksheet 和 yaml 中的 slave 关联起来
            for yexcel in self.mctx.yaml.excels:
                if os.path.basename(excel["file"]) == os.path.basename(yexcel["path"]):
                    for ws in excel["worksheets"]:
                        agileSlaveTemplate = self.menv.get_template("c_agile_slave.jinja2")
                        for yws in yexcel["sheets"]:
                            if ws["ws_name"] == yws["name"]:
                                analyseWsSet.append({
                                    "ws_name": ws["ws_name"],
                                })
                                slaves = list()

                                for idx in range(yws['slave']['count']):
                                    slave = dict()
                                    slave["file"] = os.path.basename(excel["file"])
                                    slave["sheet"] = yws["name"]
                                    slave["name"] = yws['slave']["name"]
                                    slave["table"] = ws["table"].copy()
                                    slaves.append(slave.copy())
            
                                for index, slave in enumerate(slaves):
                                    slave["id"] = yws['slave']["addresses"][index]

                                for register in ws["table"]["registers"]:
                                    if register["type"] == "enum16":
                                        register["type"] = "uint16_t"
                                    elif register["type"] == "enum32":
                                        register["type"] = "uint32_t"
                                    elif register["type"] == "bit":
                                        register["type"] = "uint16_t"
                                
                                # logger.debug(json.dumps(slave, indent=4))
                                # return
                                with open(os.path.join(outdir, f"{yws['c_code']['file_name']}.c"), "w", encoding="utf-8") as f:
                                    f.write(agileSlaveTemplate.render(
                                        slaves = slaves,
                                        slave_name=yws["slave"]["name"],
                                        table=ws["table"],
                                        excelName=os.path.basename(f"{excel['file']}"),
                                        sheetName=ws["ws_name"],
                                        version=self.mctx.version,
                                        c_code=yws["c_code"],
                                        date=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                    ))
        
        # logger.debug(json.dumps(slave, indent=4))
        for analyseWs in analyseWsSet:
            logger.info(f"成功生成C文件: {analyseWs['ws_name']}")
        for ws in excel["worksheets"]:
            index = 0
            for index, analyseWs in enumerate(analyseWsSet):
                if analyseWs["ws_name"] == ws["ws_name"]:
                    break
            if index == len(analyseWsSet):
                logger.warning(f"未解析工作表: {ws['ws_name']}")


    def generate(self):
        if self.mexcels is None:
            return

        for yamlExcelConfig in self.mctx.yaml.excels:
            for excel in self.mexcels:
                if os.path.basename(excel["file"]) == os.path.basename(yamlExcelConfig["path"]):
                    self._generateRegistersHeadFile(outdir=yamlExcelConfig["output"])
                    self._generateAgileSlaveCode(outdir=yamlExcelConfig["output"])